﻿//-----------------------------------------------------------------------
// <copyright file="TagMap.cs" company="AFS">
// Copyright (c) AFS. All rights reserved.
//
//    This file is part of Chatbox.
//
//    Chatbox is free software: you can redistribute it and/or modify
//    it under the terms of the GNU Lesser General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    any later version.
//
//    Chatbox is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
//    GNU Lesser General Public License for more details.
//
//    You should have received a copy of the GNU Lesser General Public License
//    along with Chatbox. If not, see http://www.gnu.org/copyleft/lesser.html
// </copyright>
//----------------------------------------------------------------------- 

namespace ChatBox.XamlUtility
{
    using System;
    using System.Collections.Generic;
    using System.Windows.Controls;
    using System.Windows.Documents;
    using System.Windows.Media.Imaging;

    /// <summary>
    /// Map to turn html tags to Xaml
    /// </summary>
    internal class TagMap
    {
        /// <summary>
        /// The event handlers to attach to the hyperlink
        /// </summary>
        private readonly HyperlinkContext hyperlinkContext;

        /// <summary>
        /// the tag data - how to translate html tags into xaml flowdocument
        /// </summary>
        private readonly Dictionary<string, TagMapItem> tagMapItems = 
            new Dictionary<string, TagMapItem>
            {
                { "em", new TagMapItem { WrapFunction = i => new Italic(i) } },                                                          
                { "i", new TagMapItem { WrapFunction = i => new Italic(i) } },                                                          
                { "b", new TagMapItem { WrapFunction = i => new Bold(i) } },                                                          
                { "strong", new TagMapItem { WrapFunction = i => new Bold(i) } },
                { "br", new TagMapItem { InsertFunction = () => new LineBreak() } },

                // There's nothing simple quite like a hr tag (horizontal ruled line) in xaml docs,
                // but at least we can break the line
                { "hr", new TagMapItem { InsertFunction = () => new LineBreak() } },
                { "small", new TagMapItem { WrapFunction = WrapSmall } },

                // img support is experimental
                { "img", new TagMapItem { TagWrapFunction = ConvertImage } },

                // todo: something meaningful with strikethrough text
                { "s", new TagMapItem { WrapFunction = i => new Italic(i) } },                            
                { "strike", new TagMapItem { WrapFunction = i => new Italic(i) } }     
            };

        /// <summary>
        /// Initializes a new instance of the TagMap class
        /// </summary>
        /// <param name="hyperlinkContext">the HyperlinkContext to use</param>
        public TagMap(HyperlinkContext hyperlinkContext)
        {
            this.hyperlinkContext = hyperlinkContext;

            // must do this here, cannot use instance method in initiliser
            this.tagMapItems.Add("a", new TagMapItem { TagWrapFunction = this.ConvertHyperlink });
        }

        /// <summary>
        /// Do the map
        /// </summary>
        /// <param name="tagItem">the tag data to map</param>
        /// <param name="content">the document data inside the tag</param>
        /// <returns>the xaml inline</returns>
        public Inline Map(TagItem tagItem, Inline content)
        {
            string tag = tagItem.Tag;

            if (this.tagMapItems.ContainsKey(tag))
            {
                Inline result = content;

                TagMapItem tagMapItem = this.tagMapItems[tag];
                if (tagMapItem.WrapFunction != null)
                {
                    result = tagMapItem.WrapFunction(result);
                }

                if (tagMapItem.TagWrapFunction != null)
                {
                    result = tagMapItem.TagWrapFunction(tagItem, result);
                }

                if (tagMapItem.InsertFunction != null)
                {
                    Span resultSpan = new Span();
                    resultSpan.Inlines.Add(tagMapItem.InsertFunction());
                    resultSpan.Inlines.Add(result);
                    result = resultSpan;
                }

                return result;
            }
            
            // todo: no way to parse the tag. this case is probably an error of some kind, report it
            Span contentResultSpan = new Span();
            contentResultSpan.Inlines.Add(tagItem.TextContent);
            return contentResultSpan;
        }

        /// <summary>
        /// Wrap the inline as small text
        /// </summary>
        /// <param name="inline">the text to wrap</param>
        /// <returns>the returned text</returns>
        private static Inline WrapSmall(Inline inline)
        {
            Span result = new Span(inline);
            result.FontSize = inline.FontSize * HtmlToXamlConverter.SmallFontScale;
            return result;
        }

        /// <summary>
        /// function to convet html hyperlinks to xaml
        /// </summary>
        /// <param name="tagIem">the html tag data</param>
        /// <param name="content">the content inside the hyperlink</param>
        /// <returns>the WPF hyperlink</returns>
        private Inline ConvertHyperlink(TagItem tagIem, Inline content)
        {
            Hyperlink hyperlink = new Hyperlink(content);

            string url = string.Empty;

            // should have a href on the hyperlink
            if (tagIem.TagAttributes.ContainsKey("href"))
            {
                url = tagIem.TagAttributes["href"];
            }

            if (string.IsNullOrEmpty(url))
            {
                return content;             
            }

            if (url.StartsWith("/", StringComparison.OrdinalIgnoreCase))
            {
                url = this.hyperlinkContext.SitePrefix + url;
            }

            try
            {
                hyperlink.NavigateUri = new Uri(url);
            }
            catch (UriFormatException ex)
            {
                hyperlink.NavigateUri = null;
                hyperlink.ToolTip = "Url parsing failed on '" + url + "' : " + ex.Message;
            }

            if (this.hyperlinkContext != null)
            {
                this.hyperlinkContext.SetContextOnHyperlink(hyperlink);
            }

            return hyperlink;
        }

        /// <summary>
        /// Convert a html img tag to a WPF image
        /// </summary>
        /// <param name="tagIem">the html tag data</param>
        /// <param name="content">the content inside the hyperlink</param>
        /// <returns>the wpf image</returns>
        private static Inline ConvertImage(TagItem tagIem, Inline content)
        {
            string url = string.Empty;

            // should have a src on the img
            if (tagIem.TagAttributes.ContainsKey("src"))
            {
                url = tagIem.TagAttributes["src"];
            }

            if (string.IsNullOrEmpty(url))
            {
                return new Run("Missing image src");
            }

            // todo: if the url string is relative, will have to prefix it with ... stuff or Uri() will fail
            Uri imageSource = new Uri(url);

            Image contentImage = new Image();
            contentImage.Source = new BitmapImage(imageSource);
            InlineUIContainer container = new InlineUIContainer(contentImage);
            return container;
        }
    }
}
